#include <Precomp.h>
#include <defs.h>
#include <player.h>
#include <GameWorld.h>
#include <Packet.h>
#include <ClientListener.h>

#include <Windows.h> // for outputdebugstring.

using namespace Ogre;

CGameWorld::CGameWorld(CNetrekClientListener* pListener) : m_pListener(pListener)
{
    Reset();

    Plane plane;
    plane.d = 0;
    plane.normal = -Vector3::UNIT_Z;
    float flGalaxyWidth = 1.0f * c_uiGalaxyWidth;

    m_pGridPlaneMesh = MeshManager::getSingleton().createPlane("SpaceGrid", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
                                                        plane, flGalaxyWidth, flGalaxyWidth, 1, 1, false, 1, 35.0, 35.0, -Vector3::UNIT_X);

    m_pGridPlaneEntity = m_pListener->GetSceneManager()->createEntity("SpaceGrid", "SpaceGrid");
    m_pGridPlaneEntity->setMaterialName("SpaceSkyPlane");
    m_pGridPlaneEntity->setCastShadows(false);
    m_pGridPlaneEntity->setRenderQueueGroup(RENDER_QUEUE_1);

    m_pGridPlaneNode = m_pListener->GetSceneManager()->getRootSceneNode()->createChildSceneNode("SpaceGridNode");
    m_pGridPlaneNode->attachObject(m_pGridPlaneEntity);

    m_pGridPlaneNode->setScale(1,1,1);
    m_pGridPlaneNode->setPosition(flGalaxyWidth / 2.0f, flGalaxyWidth / 2.0f,0);

    m_pLight = m_pListener->GetSceneManager()->createLight("lightLight");
    m_pLight->setType(Light::LT_DIRECTIONAL);
    m_pLight->setDirection(0.0f, 1.0f, 1.0f);

    m_pLight = m_pListener->GetSceneManager()->createLight("lightLight2");
    m_pLight->setType(Light::LT_DIRECTIONAL);
    m_pLight->setDirection(0.0f, -1.0f, -1.0f);


    m_pPhaserBillboardSet = GetSceneManager()->createBillboardSet("phaserBillboardSet", 20);
    m_pPhaserBillboardSet->setCullIndividually(true);
    m_pPhaserBillboardSet->setMaterialName("phaserMaterial");
    m_pPhaserBillboardSet->setDefaultDimensions(120.0f, 5000.0f);
    m_pPhaserBillboardSet->setBillboardType(BBT_ORIENTED_SELF);
    m_pPhaserBillboardSet->setTextureStacksAndSlices(1, 4);
    m_pPhaserBillboardSet->setBounds(AxisAlignedBox(-10000000.0f, -10000000.0f, -10000000.0f, 10000000.0f, 10000000.0f, 10000000.0f), 
                                   1000000.0f);

    m_pPhaserNode = GetSceneManager()->createSceneNode("phaserNode");
    m_pPhaserNode->attachObject(m_pPhaserBillboardSet);
    GetSceneManager()->getRootSceneNode()->addChild(m_pPhaserNode);

    m_pTorpBillboardSet = GetSceneManager()->createBillboardSet("torpBillboardSet", 200);
    m_pTorpBillboardSet->setCullIndividually(true);
    m_pTorpBillboardSet->setMaterialName("photonMaterial");
    m_pTorpBillboardSet->setDefaultDimensions(400.0f, 400.0f);
    m_pTorpBillboardSet->setBillboardRotationType(BBR_VERTEX);
    m_pTorpBillboardSet->setTextureStacksAndSlices(2, 2);
    m_pTorpBillboardSet->setBounds(AxisAlignedBox(-10000000.0f, -10000000.0f, -10000000.0f, 10000000.0f, 10000000.0f, 10000000.0f), 
                                   1000000.0f);

    m_pTorpNode = GetSceneManager()->createSceneNode("torpNode");
    m_pTorpNode->attachObject(m_pTorpBillboardSet);
    GetSceneManager()->getRootSceneNode()->addChild(m_pTorpNode);

    m_pPlasmaBillboardSet = GetSceneManager()->createBillboardSet("plasmaBillboardSet", 10);
    m_pPlasmaBillboardSet->setCullIndividually(true);
    m_pPlasmaBillboardSet->setMaterialName("photonMaterial");
    m_pPlasmaBillboardSet->setDefaultDimensions(800.0f, 800.0f);
    m_pPlasmaBillboardSet->setBillboardRotationType(BBR_VERTEX);
    m_pPlasmaBillboardSet->setTextureStacksAndSlices(2, 2);
    m_pPlasmaBillboardSet->setBounds(AxisAlignedBox(-10000000.0f, -10000000.0f, -10000000.0f, 10000000.0f, 10000000.0f, 10000000.0f), 
                                                    1000000.0f);

    m_pPlasmaNode = GetSceneManager()->createSceneNode("plasmaNode");
    m_pPlasmaNode->attachObject(m_pPlasmaBillboardSet);
    GetSceneManager()->getRootSceneNode()->addChild(m_pPlasmaNode);

}                                           


CGameWorld::~CGameWorld()
{
    Reset();
}

SceneManager* CGameWorld::GetSceneManager() 
{
    return m_pListener->GetSceneManager();
}

class FrameUpdateChild
{   
public:
    template<typename T>
    void operator() (T t) { t->FrameUpdate(); }
};

void CGameWorld::FrameUpdate()
{
    FireSimpleEvent(eGameWorldEventID_BeginFrameUpdate);

    for_each(m_rgpPlayers.begin(), m_rgpPlayers.end(), FrameUpdateChild());
    for_each(m_rgpPlanets.begin(), m_rgpPlanets.end(), FrameUpdateChild());
    for_each(m_rgpTorps.begin(), m_rgpTorps.end(), FrameUpdateChild());
    for_each(m_rgpPlasmas.begin(), m_rgpPlasmas.end(), FrameUpdateChild());
    for_each(m_rgpPhasers.begin(), m_rgpPhasers.end(), FrameUpdateChild());

    FireSimpleEvent(eGameWorldEventID_EndFrameUpdate);
}

// This function is currently called far too often for what is needed. TODO - Fix this. 
void CGameWorld::Reset()
{
    auto_ptr<CLocalPlayer> localPlayer(new CLocalPlayer(this));
    m_pLocalPlayer = localPlayer;

    for_each(m_rgpPlayers.begin(), m_rgpPlayers.end(), DeleteChild());
    m_rgpPlayers.clear();

    for_each(m_rgpPlanets.begin(), m_rgpPlanets.end(), DeleteChild());
    m_rgpPlanets.clear();

    for_each(m_rgpTorps.begin(), m_rgpTorps.end(), DeleteChild());
    m_rgpTorps.clear();

    for_each(m_rgpPlasmas.begin(), m_rgpPlasmas.end(), DeleteChild());
    m_rgpPlasmas.clear();

    for_each(m_rgpPhasers.begin(), m_rgpPhasers.end(), DeleteChild());
    m_rgpPhasers.clear();

    for_each(m_rgpShips.begin(), m_rgpShips.end(), DeleteChild());
    m_rgpShips.clear();

    auto_ptr<CGalaxyStatus> galaxyStatus(new CGalaxyStatus(this));
    m_pGalaxyStatus = galaxyStatus;

    m_rgChatMessages.clear();

    m_strMOTDMessage = "";
}

CLocalPlayer* CGameWorld::GetLocalPlayer()
{
    return m_pLocalPlayer.get();
}

CGalaxyStatus* CGameWorld::GetGalaxyStatus()
{
    return m_pGalaxyStatus.get();
}

CGamePlayer* CGameWorld::GetPlayerForLocalPlayer()
{
    return GetPlayerByID(GetLocalPlayer()->GetPlayerID());
}


CGamePlayer* CGameWorld::GetPlayerByID(DWORD dwPlayerID)
{
    for(unsigned int i = 0; i < m_rgpPlayers.size(); i++)
    {
        if(m_rgpPlayers[i]->GetPlayerID() == dwPlayerID)
        {
            return m_rgpPlayers[i];
        }
    }

    m_rgpPlayers.push_back(new CGamePlayer(dwPlayerID, this));

    return m_rgpPlayers[m_rgpPlayers.size() - 1];
}

CShipData* CGameWorld::GetShipByType(EShipType eShipType)
{
    for(unsigned int i = 0; i < m_rgpShips.size(); i++)
    {
        if(m_rgpShips[i]->GetShipType() == eShipType)
        {
            return m_rgpShips[i];
        }
    }

    m_rgpShips.push_back(new CShipData(eShipType));

    return m_rgpShips[m_rgpShips.size() - 1];
}


CPlanet* CGameWorld::GetPlanetByID(DWORD dwPlanetID)
{
    for(unsigned int i = 0; i < m_rgpPlanets.size(); i++)
    {
        if(m_rgpPlanets[i]->GetPlanetID() == dwPlanetID)
        {
            return m_rgpPlanets[i];
        }
    }

    m_rgpPlanets.push_back(new CPlanet(dwPlanetID, this));
            
    return m_rgpPlanets[m_rgpPlanets.size() - 1];
}

CPlanet* CGameWorld::GetPlanetByLocation(DWORD dwXLocation, DWORD dwYLocation, float flTolerance)
{   
    float distSquared = (float) (c_uiOrbitRadius * c_uiOrbitRadius);
    float distCheck = distSquared * flTolerance * flTolerance;

    for(CPlanetList::iterator i = m_rgpPlanets.begin(); i < m_rgpPlanets.end(); ++i)
    {
        CPlanet* pPlanet = *i;

        float xDiff = (float)((int)(pPlanet->GetXLocation()) - (int) dwXLocation);
        float yDiff = (float)((int)(pPlanet->GetYLocation()) - (int) dwYLocation);

        if(xDiff * xDiff + yDiff*yDiff < distCheck)
        {
            return pPlanet;
        }
    }

    return NULL;
}


CTorp* CGameWorld::GetTorpByID(DWORD dwTorpID)
{
    for(unsigned int i = 0; i < m_rgpTorps.size(); i++)
    {
        if(m_rgpTorps[i]->GetTorpID() == dwTorpID)
        {
            return m_rgpTorps[i];
        }
    }

    m_rgpTorps.push_back(new CTorp(dwTorpID, this));

    return m_rgpTorps[m_rgpTorps.size() - 1];
}

CPlasma* CGameWorld::GetPlasmaByID(DWORD dwPlasmaID)
{
    for(unsigned int i = 0; i < m_rgpPlasmas.size(); i++)
    {
        if(m_rgpPlasmas[i]->GetPlasmaID() == dwPlasmaID)
        {
            return m_rgpPlasmas[i];
        }
    }

    m_rgpPlasmas.push_back(new CPlasma(dwPlasmaID, this));

    return m_rgpPlasmas[m_rgpPlasmas.size() - 1];
}

CPhaser* CGameWorld::GetPhaserByID(DWORD dwPhaserID)
{
    for(unsigned int i = 0; i < m_rgpPhasers.size(); i++)
    {
        if(m_rgpPhasers[i]->GetPhaserID() == dwPhaserID)
        {
            return m_rgpPhasers[i];
        }
    }

    m_rgpPhasers.push_back(new CPhaser(dwPhaserID, this));

    return m_rgpPhasers[m_rgpPhasers.size() - 1];
}

void CGameWorld::ProcessPacket(EServerPacketType ePacketType, const packet_base* pPacketServer)
{

    switch(ePacketType)
    {
    case EServerPacketType_MOTD:
        {
            const motd_spacket* pPacket = (const motd_spacket*) pPacketServer;
            m_strMOTDMessage += pPacket->line;
        }
        break;

    case EServerPacketType_NewPlayerLogin:
        {
            const plyr_login_spacket* pPacket = (const plyr_login_spacket*) pPacketServer;

            int iPlayerID = pPacket->pnum;
            GetPlayerByID(iPlayerID)->HandlePlayerLogin(pPacket);
        }
        break;

    case EServerPacketType_PlayerHostilitySettings:
        {
            const hostile_spacket* pPacket = (const hostile_spacket*) pPacketServer;

            int iPlayerID = pPacket->pnum;
            GetPlayerByID(iPlayerID)->HandlePlayerHostilitySettings(pPacket);
        }
        break;

    case EServerPacketType_PlayerInfo:
        {
            const plyr_info_spacket* pPacket = (const plyr_info_spacket*) pPacketServer;

            int iPlayerID = pPacket->pnum;
            GetPlayerByID(iPlayerID)->HandlePlayerInfo(pPacket);
        }
        break;

    case EServerPacketType_PlayerKills:
        {
            const kills_spacket* pPacket = (const kills_spacket*) pPacketServer;

            int iPlayerID = pPacket->pnum;
            GetPlayerByID(iPlayerID)->HandlePlayerKills(pPacket);
        }
        break;

    case EServerPacketType_PlayerStatus:
        {
            const pstatus_spacket* pPacket = (const pstatus_spacket*) pPacketServer;

            int iPlayerID = pPacket->pnum;
            GetPlayerByID(iPlayerID)->HandlePlayerStatus(pPacket);
        }
        break;

    case EServerPacketType_PlayerFlags:
        {
            const flags_spacket* pPacket = (const flags_spacket*) pPacketServer;

            int iPlayerID = pPacket->pnum;
            GetPlayerByID(iPlayerID)->HandlePlayerFlags(pPacket);
        }
        break;

    case EServerPacketType_PlayerStats:
        {
            const stats_spacket* pPacket = (const stats_spacket*) pPacketServer;

            int iPlayerID = pPacket->pnum;
            GetPlayerByID(iPlayerID)->HandlePlayerStats(pPacket);
        }
        break;

    case EServerPacketType_PlayerLocation:
        {
            const player_spacket* pPacket = (const player_spacket*) pPacketServer;

            int iPlayerID = pPacket->pnum;
            GetPlayerByID(iPlayerID)->HandlePlayerLocation(pPacket);
        }
        break;

    case EServerPacketType_You:
        {
            const you_spacket* pPacket = (const you_spacket*) pPacketServer;

            m_pLocalPlayer->HandleYouPacket(pPacket);
        }
        break;

    case EServerPacketType_PlanetNameAndLocation:
        {
            const planet_loc_spacket* pPacket = (const planet_loc_spacket*) pPacketServer;

            int iPlanetID = pPacket->pnum;

            GetPlanetByID(iPlanetID)->HandlePlanetNameAndLocation(pPacket);
        }
        break;

    case EServerPacketType_PlanetResourceInfo:
        {
            const planet_spacket* pPacket = (const planet_spacket*) pPacketServer;

            int iPlanetID = pPacket->pnum;

            GetPlanetByID(iPlanetID)->HandlePlanetResourceInfo(pPacket);
        }
        break;

    case EServerPacketType_TorpStatus:
        {
            const torp_info_spacket* pPacket = (const torp_info_spacket*) pPacketServer;

            int iTorpID = pPacket->tnum;

            GetTorpByID(iTorpID)->HandleTorpStatus(pPacket);
        }
        break;

    case EServerPacketType_TorpLocation:
        {
            const torp_spacket* pPacket = (const torp_spacket*) pPacketServer;

            int iTorpID = pPacket->tnum;

            GetTorpByID(iTorpID)->HandleTorpLocation(pPacket);
        }
        break;

    case EServerPacketType_PlasmaStatus:
        {
            const plasma_info_spacket* pPacket = (const plasma_info_spacket*) pPacketServer;

            int iPlasmaID = pPacket->pnum;

            GetPlasmaByID(iPlasmaID)->HandlePlasmaStatus(pPacket);
        }
        break;

    case EServerPacketType_PlasmaLocation:
        {
            const plasma_spacket* pPacket = (const plasma_spacket*) pPacketServer;

            int iPlasmaID = pPacket->pnum;

            GetPlasmaByID(iPlasmaID)->HandlePlasmaLocation(pPacket);
        }
        break;

    case EServerPacketType_Phaser:
        {
            const phaser_spacket* pPacket = (const phaser_spacket*) pPacketServer;

            int iPhaserID = pPacket->pnum;

            GetPhaserByID(iPhaserID)->HandlePhaser(pPacket);
        }
        break;

    case EServerPacketType_ValidTeamMask:
        {
            const mask_spacket* pPacket = (const mask_spacket*) pPacketServer;

            m_pGalaxyStatus->HandleValidTeamMask(pPacket);
        }
        break;

    case EServerPacketType_Message:
        {
            const mesg_spacket* pPacket = (const mesg_spacket*) pPacketServer;

            m_rgChatMessages.push_back(*pPacket);

            unsigned int size = m_rgChatMessages.size();

            while(size > c_maxChatMessages)
            {
                m_rgChatMessages.pop_front();
                size--;
            }

            CChatEvent chatEvent(pPacket);

            FireEvent(&chatEvent);
        }
        break;

    default:
        char szBuf[240];

        sprintf_s(szBuf, "Unhandled Packet type: %s\n", CPacket::GetPacketName(ePacketType), 240);
        OutputDebugStringA(szBuf);

    }
}

void CGameWorld::FireEvent(CGameWorldEvent* pGameWorldEvent)
{
    for(EventSubscriberList::iterator i = m_rgEventSubscribers.begin(); i < m_rgEventSubscribers.end(); ++i)
    {
        (*i)(pGameWorldEvent);
    }
}

void CGameWorld::FireSimpleEvent(EGameWorldEventID eEventID)
{
    CGameWorldEvent playerUpdatedEvent(eEventID);
    FireEvent(&playerUpdatedEvent);
}

CGameWorld::CEventSubscription* CGameWorld::SubscribeEvents(const EventSubscriber& eventSubscriber)
{
    m_rgEventSubscribers.push_back(eventSubscriber);

    return new CEventSubscription(eventSubscriber);
}


void CGameWorld::UnsubscribeEvents(CEventSubscription* pSubscription)
{
    for(EventSubscriberList::iterator i = m_rgEventSubscribers.begin(); i < m_rgEventSubscribers.end(); ++i)
    {
        if(pSubscription->GetEventSubscriber().IsEquivalentTo(*i))
        {
            m_rgEventSubscribers.erase(i);
            pSubscription->GetEventSubscriber().release();
            break;
        }
    }

    delete pSubscription;
}

float CGameWorld::GetTimeSinceLastFrameAsFloat() const
{
    return m_pListener->GetTimeSinceLastFrameAsFloat();
}



const char* CGameWorld::GetShipMeshFileName(ETeamID eTeamID, EShipType eShipType) const
{
    static char szNameBuf[80];

    if(eShipType == eShipType_SGalaxy || eShipType == eShipType_Att)
    {
        eShipType = eShipType_Assault;
    }

    if(eTeamID == eTeamID_None)
    {
        eShipType = eShipType_Cruiser;
    }

    sprintf_s(szNameBuf, "%s-%s.mesh", ETeamIDToString(eTeamID), EShipTypeToString(eShipType), 80);

    return szNameBuf;
}


